#include "Aws_mqtt_connect.h"
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>

#include "esp_system.h"
#include "esp_event.h"
#include "protocol_examples_common.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_sleep.h"

#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
#include "cJSON.h"
#include "esp_log.h"

#include "mqtt_client.h"
#include "Datapara.h"
#include "WiFiIno_flash.h"
#include "TimerConfig.h"
#include "wifi_apstation.h"
#include "OTA.h"
#include "Datapara.h"


OTA_Ino_Type_t *ptr_ota_info = &AWS_OTA_Ino;
LocationBatIno_Type_t * ptr_LocationBat_ino = &LocationBatIno;

char topic_notify_next_include_deviceid[50];  // 根据可能的最长字符串长度定义数组大小，这里假设最长50个字符
char topic_get_include_thingname[50];   //订阅主题拼接
char topic_get_accept_include_thingname[60];  //订阅主题拼接

#define OTA_TOPIC_NOTIFY_NEXT topic_notify_next_include_deviceid
#define OTA_TOPIC_GET  topic_get_include_thingname
#define OTA_TOPIC_GET_ACCEPT topic_get_accept_include_thingname

#define CONFIGRATION_TOPIC "upload/dustbin_data"  //上传事件主题




static const char *TAG = "mqtts_example";


//指向AWS IoT Core设备证书的收尾地址
extern const uint8_t client_cert_pem_start[] asm("_binary_client_crt_start");
extern const uint8_t client_cert_pem_end[] asm("_binary_client_crt_end");
extern const uint8_t client_key_pem_start[] asm("_binary_client_key_start");
extern const uint8_t client_key_pem_end[] asm("_binary_client_key_end");
extern const uint8_t server_cert_pem_start[] asm("_binary_mosquitto_org_crt_start");
extern const uint8_t server_cert_pem_end[] asm("_binary_mosquitto_org_crt_end");



//将各项位置信息数据打包成json格式
char* JsonDataAnalays()
{
    // 解析整个自定义数据上传的JSON对象
    uploaddata = cJSON_CreateObject();
    LocationBatIno.position_id = 3000; //静态ID
    LocationBatIno.battery_flag = 100; //电池电量

    
    
    cJSON_AddNumberToObject(uploaddata, "device_id", LocationBatIno.Dev_id);
    cJSON_AddNumberToObject(uploaddata, "building_id", LocationBatIno.building_id);
    cJSON_AddNumberToObject(uploaddata, "floor_id", LocationBatIno.floor_id);
    cJSON_AddNumberToObject(uploaddata, "room_id", LocationBatIno.room_id);
    cJSON_AddNumberToObject(uploaddata, "device_location_id", LocationBatIno.device_location_id);
    cJSON_AddNumberToObject(uploaddata, "device_type_id", LocationBatIno.device_type_id);
    cJSON_AddNumberToObject(uploaddata, "device_position_id", LocationBatIno.position_id);
    cJSON_AddNumberToObject(uploaddata, "dustbin_flag", LocationBatIno.distance);
    cJSON_AddNumberToObject(uploaddata, "battery_flag", LocationBatIno.battery_flag);
    // cJSON_AddStringToObject(uploaddata, "STA_IP_Addr", ip_str);
    char *json_str = cJSON_Print(uploaddata);

    uploaddata = NULL; 
    return json_str;
}

char* OTA_JsonDataAnalays()
{
    uploaddata = cJSON_CreateObject();

   
    cJSON_AddStringToObject(uploaddata, "JobId", "OTA_DEMO");
    cJSON_AddStringToObject(uploaddata, "thingName", "Regulus");
    cJSON_AddNumberToObject(uploaddata, "includeJobDocument", 1);  //0:不包含JobDocument  1：包含JobDocument

    char *json_str = cJSON_Print(uploaddata);
    uploaddata = NULL; // 避免误删
    return json_str;
}


/**
 * @brief 创建一个JSON字符串，用于更新OTA状态
 * 
 * 该函数创建一个JSON对象，包含OTA更新的状态信息，并将其转换为字符串。
 * 
 * @return 返回一个指向JSON字符串的指针，该字符串需要在使用后释放
 */
char* OTA_UpdateStatus()
{
    uploaddata = cJSON_CreateObject();

    if(OTA_FINISH_OR_FAIL == 1){
        cJSON_AddStringToObject(uploaddata, "status", "SUCCEEDED");
    } else  {
        cJSON_AddStringToObject(uploaddata, "status", "IN_PROGRESS");
    }
    
    cJSON_AddStringToObject(uploaddata, "thingName", "1.0");
    cJSON_AddNumberToObject(uploaddata, "includeJobExecutionState", 1);  //0:不包含JobDocument  1：包含JobDocument
    cJSON_AddNumberToObject(uploaddata, "includeJobDocument", 1);  //0:不包含JobDocument  1：包含JobDocument
    // cJSON_AddNumberToObject(uploaddata, "stepTimeoutInMinutes", 1);  //超时时间

    char *json_str = cJSON_Print(uploaddata);

    uploaddata = NULL; // 避免误删
    return json_str;
}



static void log_error_if_nonzero(const char *message, int error_code)
{
    if (error_code != 0) {
        ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
    }
}

/*
 * @brief Event handler registered to receive MQTT events
 *
 *  This function is called by the MQTT client event loop.
 *
 * @param handler_args user data registered to the event.
 * @param base Event base for the handler(always MQTT Base in this example).
 * @param event_id The id for the received event.
 * @param event_data The data for the event, esp_mqtt_event_handle_t.
 */

int ota_notify_id = 0, ota_job_get_id = 0, ota_update_id = 0;
void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32, base, event_id);
    esp_mqtt_event_handle_t event = event_data;
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    switch ((esp_mqtt_event_id_t)event_id) {
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");

        msg_id = esp_mqtt_client_subscribe(client, CONFIGRATION_TOPIC, 0);   //订阅配置主题
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);

      
        ota_notify_id = esp_mqtt_client_subscribe(client, OTA_TOPIC_NOTIFY_NEXT, 0);
        ESP_LOGI(TAG, "sent subscribe successful, ota_notify_id=%d", ota_notify_id);

        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        break;

    case MQTT_EVENT_SUBSCRIBED:

        /*等待上传    20s传一次 成功控制 有效载荷定时上传  放到jsonDataAnalays前面，避免首次上传信息时出现测距信息为空的情况*/
        //xEventGroupWaitBits(timer_event_group, MQTTIno_Upload_START, pdTRUE, pdFALSE, portMAX_DELAY);

        /*等待测距数据更新后再上传，至少不上传空的距离信息*/
        xEventGroupWaitBits(s_HC_SR04_event_group, RANGING_END, pdTRUE, pdFALSE, portMAX_DELAY);

        

        char * json_data = JsonDataAnalays();   //推送位置信息json数据到配置主题
        ESP_LOGI(TAG, "json_str的信息:%s", json_data);   //调试阶段使用，现在暂不使用
        msg_id = esp_mqtt_client_publish(client, CONFIGRATION_TOPIC, json_data, strlen(json_data), 0, 0);
        ESP_LOGI(TAG, "sent position_json_data publish successful, msg_id=%d", msg_id);
        free(json_data);


        ESP_LOGI(TAG, "device deep-sleep 未识别到有效URL,设备进入深度睡眠");
        esp_deep_sleep_start(); // 进入深度睡眠       //mqtt代理服务器测试用，测试完毕后记得注释掉


        char * ota_json_data = OTA_JsonDataAnalays();  //发布json数据到get主题获取执行列表
        ota_job_get_id = esp_mqtt_client_publish(client, OTA_TOPIC_GET, ota_json_data, strlen(ota_json_data), 0, 0);
        ESP_LOGI(TAG, "sent job_get_json publish successful, ota_job_get_id=%d", ota_job_get_id);
        free(ota_json_data);
        break;
    case MQTT_EVENT_UNSUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_PUBLISHED:
        ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_DATA:
        ESP_LOGI(TAG, "MQTT_EVENT_DATA");
        printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
        printf("DATA=%.*s\r\n", event->data_len, event->data);

        // 将获取的主题尾部添加\0以方便进行字符串比较
        // 分配足够空间的新字符串数组（多分配1个字节用于存放字符串结束符'\0'）
        char* new_topic = (char*)malloc((event->topic_len + 1) * sizeof(char));
        char* new_topic_data = (char*)malloc((event->data_len + 1) * sizeof(char));

        // 使用strncpy函数进行字符串复制，确保不会越界  尾部添加一个'\0'保证后续调用strcmp
        strncpy(new_topic, event->topic, event->topic_len);
        new_topic[event->topic_len] = '\0';

 
        char * ptr_ota_topic_notify = OTA_TOPIC_GET_ACCEPT;
        // ESP_LOGI(TAG, "event->topic 的值为: %s", event->topic);
        // ESP_LOGI(TAG, "ptr_ota_topic_notify 接收到的主题为: %s", ptr_ota_topic_notify);
        // ESP_LOGI(TAG, "OTA_TOPIC_GET_ACCEPT的主题为: %s", OTA_TOPIC_GET_ACCEPT);

        //AWS通过topic_notify下发的主题和OTA_TOPIC_GET_ACCEPT一致时，代表有job任务下发
        if (strcmp(new_topic, ptr_ota_topic_notify) == 0) {

            strncpy(new_topic_data, event->data, event->data_len);   //提取出job任务下的作业文件
            new_topic_data[event->data_len] = '\0';

            
            // ESP_LOGI(TAG, "请等待,大约1min后将进行OTA升级");
            // xEventGroupWaitBits(timer_event_group, OTA_Upload_START, pdTRUE, pdFALSE, portMAX_DELAY); //等待1min后在进行OTA url提取以及ota升级避免频繁ota
            

            //分析Job文件提取出url以及jobId
            OTA_Job_Data_Deal( new_topic_data );

            if(AWS_OTA_Ino.Valid_URL == true){  //如果URL有效
              ESP_LOGI(TAG, "AWS_OTA_Ino.Valid_URL = 1");
            }else{
              ESP_LOGI(TAG, "AWS_OTA_Ino.Valid_URL = 0");
            }

            if(AWS_OTA_Ino.Valid_URL == true)  //如果URL有效
            {
                //获取jobId，并将jobid拼接成topic_update，最终用于OTA升级状态上报
                char topic_update[60];  // 根据可能的最长字符串长度定义数组大小，这里假设最长50个字符
                snprintf(topic_update, sizeof(topic_update), "$aws/things/%s/jobs/%s/update", LocationBatIno.device_id, ptr_ota_info->jobId); // 使用 snprintf 进行字符串拼接

                xEventGroupWaitBits(ota_event_group, OTA_IN_PROGRESS, pdTRUE, pdFALSE, portMAX_DELAY);   //等待OTA数据传输开始
                char * json_ota_update_data = OTA_UpdateStatus();   //OTA "IN PROGRESS"状态上报
                char * json_ota_updatota_update_id = esp_mqtt_client_publish(client, topic_update, json_ota_update_data, strlen(json_ota_update_data), 0, 0);   //OTA升级状态上报
                
                ESP_LOGI(TAG, "sent ota_updataino publish successful, ota_update_id=%d", ota_update_id);
                
    
                xEventGroupWaitBits(ota_event_group, OTA_SUCCEEDED, pdTRUE, pdFALSE, portMAX_DELAY);  //等待OTA数据传输完成

                char * ota_update_success_data = OTA_UpdateStatus();   //OTA "SUCCESS"状态上报
                ota_update_id = esp_mqtt_client_publish(client, topic_update, ota_update_success_data, strlen(ota_update_success_data), 0, 0);
                
                ESP_LOGI(TAG, "sent ota_updataino publish successful, ota_update_id=%d", ota_update_id);

                xEventGroupSetBits(ota_event_group, OTA_UPDATA_FINISH);  //等待OTA完成

                // 使用完后，释放内存
                free(json_ota_update_data);
                free(ota_update_success_data);
                free(new_topic);
            }
            else  //没有job时进入深度睡眠
            {
                ESP_LOGI(TAG, "device deep-sleep 未识别到有效URL,设备进入深度睡眠");
                esp_deep_sleep_start(); // 进入深度睡眠
            }

        }
        break;


    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
            log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
            log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
            log_error_if_nonzero("captured as transport's socket errno",  event->error_handle->esp_transport_sock_errno);
            ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
        }
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;
    
    }
}

void mqtt_app_start(void)
{
    const esp_mqtt_client_config_t mqtt_cfg = {
        //指定了MQTT代理服务器的地址和端口，以及使用的协议
 
        .broker.address.uri = "mqtts://ec2-54-198-208-191.compute-1.amazonaws.com", //客户EC2下的mqtt服务器
        .broker.address.port = 8883,

         //该指针指向存储在二进制文件中的服务器证书的起始位置。
        .broker.verification.certificate = (const char *)server_cert_pem_start,
        // .broker.verification.certificate = NULL,
        .credentials = {
        .authentication = {
            .certificate = (const char *)client_cert_pem_start,//该指针指向存储在二进制文件中的客户端证书的起始位置。
            .key = (const char *)client_key_pem_start, //该指针指向存储在二进制文件中的客户端私钥的起始位置。
        },
        .client_id = LocationBatIno.device_id,  //将配网阶段所写入的设备id赋值给client id
        }
    };


    
  
    ota_event_group = xEventGroupCreate(); //创建ota事件组

    //拼接notify主题
    snprintf(topic_notify_next_include_deviceid, sizeof(topic_notify_next_include_deviceid), "$aws/things/%s/jobs/notify-next", ptr_LocationBat_ino->device_id); // 使用 snprintf 进行字符串拼接
    
    //拼接GET主题
    snprintf(topic_get_include_thingname, sizeof(topic_get_include_thingname), "$aws/things/%s/jobs/$next/get", ptr_LocationBat_ino->device_id); // 使用 snprintf 进行字符串拼接

    //拼接GET_ACCEPT主题
    snprintf(topic_get_accept_include_thingname, sizeof(topic_get_accept_include_thingname), "$aws/things/%s/jobs/$next/get/accepted", ptr_LocationBat_ino->device_id); // 使用 snprintf 进行字符串拼接

    ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());
    esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
    /* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
    esp_mqtt_client_start(client);
}


